﻿using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Media.IngestClient
{
    /// <summary>
    /// Implementation of IPushOutputConsumer that can stream the output from PushClient to an http destination.
    /// </summary>
    public sealed class HttpPushOutputConsumer : IPushOutputConsumer, IDisposable
    {
        const string UserAgent = "IngestClient";

        CancellationTokenSource cts;
        Task currentTask;

        /// <summary>
        /// Creates a new instance of HttpPushOutputConsumer.
        /// </summary>
        public HttpPushOutputConsumer()
        {
            StreamIdentifier = "Encoder1";
        }

        /// <summary>
        /// Gets or sets the channel ingest URL to send data to.
        /// </summary>
        public Uri IngestionUrl { get; set; }

        /// <summary>
        /// Gets or sets the name of the stream used when sending data to the the ingest service. This can be set to anything.
        /// </summary>
        public string StreamIdentifier { get; set; }

        /// <summary>
        /// Gets the most recent Http response status code.
        /// </summary>
        public HttpStatusCode HttpResponseStatusCode { get; private set; }

        /// <summary>
        /// Initializes the session by connecting to the ingest server. Use this to endpoint will accept data before sending data.
        /// </summary>
        /// <param name="c">A cancellation token that can be used to signal that the operation should terminate as soon as possible.</param>
        /// <returns>An awaitable action.</returns>
        public async Task InitializeAsync(CancellationToken c)
        {
            HttpResponseStatusCode = HttpStatusCode.OK;
            cts = new CancellationTokenSource();
            var request = (HttpWebRequest)WebRequest.CreateHttp(IngestionUrl);
            request.Headers["User-Agent"] = UserAgent;
            var t = request.GetResponseAsync();
            try
            {
                using (var response = (HttpWebResponse)(await t))
                {
                    HttpResponseStatusCode = response.StatusCode;
                    //TODO: response.EnsureSuccessStatusCode();
                }
            }
            finally
            {
                currentTask = null;
            }
        }

        /// <inheritdoc />
        public async Task SendDataAsync(Stream inputStream, CancellationToken cancellationToken, IProgress<ulong> progress)
        {
            HttpResponseStatusCode = HttpStatusCode.OK;
            Uri encoderEndpoint = !string.IsNullOrEmpty(StreamIdentifier) ? new Uri(IngestionUrl, string.Format("{0}/Streams({1})", IngestionUrl.LocalPath, StreamIdentifier)) : IngestionUrl;

            var request = (HttpWebRequest)WebRequest.CreateHttp(encoderEndpoint);
            using (var c = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token))
            {
                var t = request.PostAsync(inputStream, c.Token, progress);
                currentTask = t;
                try
                {
                    using (var response = await t)
                    {
                        HttpResponseStatusCode = response.StatusCode;
                        //TODO: response.EnsureSuccessStatusCode();
                    }
                }
                finally
                {
                    currentTask = null;
                }
            }
        }

        /// <inheritdoc />
        public async Task TerminateAsync()
        {
            cts.Cancel();
            if (currentTask != null)
            {
                try
                {
                    await currentTask;
                }
                catch { /* ignore, we just want to make sure everything is unwired before continuing. */ }
                currentTask = null;
            }
            cts.Dispose();
            cts = null;
        }

        /// <inheritdoc />
        public void Dispose()
        {
            var noawait = TerminateAsync();
        }
    }
}
